﻿#region usings
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Net;
using System.Runtime.Serialization;
using System.Threading;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using System.Linq;
using InsuranceAdjuster.OccasionallyConnected.Messages;
using InsuranceAdjuster.Services;
#endregion
namespace InsuranceAdjuster.OccasionallyConnected {
    [DataContract]
    public abstract partial class Message : INotifyPropertyChanged {
        public Message() {
            MessageGuid = Guid.NewGuid();
            Status = MessageStatus.Created;
            CacheRules = MessageDataSource.CacheAndServer;
            CurrentEntityManager = EntityManager.Current;
            GroupName = "Action";
            MessageData = new Dictionary<String, Object>();
            RequestEntities = new Dictionary<String, MessageResult>();
            ResponseEntities = new Dictionary<String, MessageResult>();
            MessageName = this.GetType().Name;
        }
        

        [DataMember]
        public Guid MessageGuid { get; set; }

        [DataMember]
        public MessageDataSource CacheRules { get; set; }
        
        [DataMember]
        public String MessageIdentifier { get; set; }
        
        [DataMember]
        public Boolean AutoClearOnArchive { get; set; }

        [DataMember]
        public String GroupName { get; set; }

        [DataMember]
        public String MessageName { get; set; }

        private MessageStatus _Status = MessageStatus.Created;
        [DataMember]
        public MessageStatus Status {
            get { return _Status; }
            set {
                if (_Status == value)
                    return;
                _Status = value;
                RaisePropertyChanged("Status");
                RaisePropertyChanged("StatusText");
                RaisePropertyChanged("IsPending");
                RaisePropertyChanged("IsCompleted");
                RaisePropertyChanged("IsActive");
            }
        }
        public String StatusText {
            get {
                switch (Status) {
                    case MessageStatus.Aborted:
                        return "Aborted";
                    case MessageStatus.Completed:
                        return "Completed";
                    case MessageStatus.Created:
                        return "In Queue";
                    case MessageStatus.InQueue:
                        return "In Queue";
                    case MessageStatus.Preparing:
                        return "Preparing";
                    case MessageStatus.ProcessingCache:
                        return "Checking Cache...";
                    case MessageStatus.ProcessingServer:
                        return "Running...";
                    case MessageStatus.Redundent:
                        return "Redundent";
                    case MessageStatus.WaitingForNetworkConnection:
                        return "Waiting Offline";
                    default:
                        return "Unknown Status";
                }
            }
        }

        private int _IsBusyRefCount = 0;
        private int _IsInFlightRefCount = 0;
        public Boolean IsInFlight {
            get { return _IsInFlightRefCount > 0; }
        }
        
        public Boolean IsPending {
            get {
                return Status == MessageStatus.Created
                    || Status == MessageStatus.InQueue
                    || Status == MessageStatus.WaitingForNetworkConnection
                    || Status == MessageStatus.ProcessingCache
                    || Status == MessageStatus.Preparing;
            }
        }
        
        public Boolean IsBusy {
            get {
                if (IsInFlight)
                    return true;
                return _IsBusyRefCount > 0;
            }
        }
        
        public Boolean IsCompleted {
            get {
                return Status == MessageStatus.Completed
                    || Status == MessageStatus.Redundent
                    || Status == MessageStatus.Aborted;
            }
        }
        
        public Boolean IsActive {
            get {
                return Status != MessageStatus.Completed
                    && Status != MessageStatus.Redundent
                    && Status != MessageStatus.Aborted;
            }
        }

        private Boolean _IsDurable = false;
        [DataMember]
        public Boolean IsDurable {
            get { return _IsDurable; }
            set {
                if (_IsDurable == value)
                    return;
                _IsDurable = value;
                RaisePropertyChanged("IsDurable");
            }
        }

        public Boolean IsCleared {
            get {
                return MessageData == null
                    && RequestEntities == null
                    && ResponseEntities == null;
            }
        }

        private Guid _CallerGuid = Guid.Empty;
        [DataMember]
        public Guid CallerGuid {
            get { return _CallerGuid; }
            set {
                if (_CallerGuid == value)
                    return;
                _CallerGuid = value;
                RaisePropertyChanged("CallerGuid");
            }
        }

        private Boolean? _ReturnedCacheResults = null;
        [DataMember]
        public Boolean? ReturnedCacheResults {
            get { return _ReturnedCacheResults; }
            set {
                if (_ReturnedCacheResults == value)
                    return;
                _ReturnedCacheResults = value;
                RaisePropertyChanged("ReturnedCacheResults");
            }
        }

        private Boolean? _ReturnedServerResults = null;
        [DataMember]
        public Boolean? ReturnedServerResults {
            get { return _ReturnedServerResults; }
            set {
                if (_ReturnedServerResults == value)
                    return;
                _ReturnedServerResults = value;
                RaisePropertyChanged("ReturnedServerResults");
            }
        }

        private DateTime? _ExecutionStartedAt = null;
        [DataMember]
        public DateTime? ExecutionStartedAt {
            get { return _ExecutionStartedAt; }
            set {
                if (_ExecutionStartedAt == value)
                    return;
                _ExecutionStartedAt = value;
                RaisePropertyChanged("ExecutionStartedAt");
                RaisePropertyChanged("ExecutionDuration");
            }
        }

        private DateTime? _ExecutionFinishedAt = null;
        [DataMember]
        public DateTime? ExecutionFinishedAt {
            get { return _ExecutionFinishedAt; }
            set {
                if (_ExecutionFinishedAt == value)
                    return;
                _ExecutionFinishedAt = value;
                RaisePropertyChanged("ExecutionFinishedAt");
                RaisePropertyChanged("ExecutionDuration");
            }
        }

        public TimeSpan? ExecutionDuration {
            get {
                if (ExecutionStartedAt == null
                    || ExecutionFinishedAt == null)
                    return null;
                else
                    return ExecutionFinishedAt - ExecutionStartedAt;
            }
        }
        
        //protected List<Message> SubMessages { get; set; }
        [DataMember]
        public Dictionary<String, Object> MessageData { get; set; }

        [DataMember]
        public Dictionary<String, MessageResult> RequestEntities { get; set; }
        
        [DataMember]
        public Dictionary<String, MessageResult> ResponseEntities { get; set; }

        private EntityManager _EntityManager = null;
        protected EntityManager CurrentEntityManager {
            get {
                if (_EntityManager == null)
                    _EntityManager = EntityManager.Current;
                return _EntityManager;
            }
            set {
                _EntityManager = value;
            }
        }

        virtual public void Archive() {
            RequestEntities.Clear();
            ResponseEntities.Clear();

            if (AutoClearOnArchive)
                Clear();
        }

        virtual public void Clear() {

            MessageData = null;
            RaisePropertyChanged("IsCleared");

            CurrentEntityManager.GetObjectStore().UpdateDurableMessage(this);
            CurrentEntityManager = null;
        }

        override public string ToString() {
            return GetType().Name;
        }

        public Boolean IsPersisted() {
            return EntityManager.Current.GetObjectStore().IsMessagePersisted(this);
        }

        virtual protected Boolean PreProcessAction() {
            return true;
        }

        virtual protected void OnlineOperation() {
        }

        virtual protected void PostProcessAction() {
        }

        virtual protected List<MessageResult> OfflineOperation(IObjectStore objectStore) {
            return new List<MessageResult>();
        }

        virtual protected List<MessageResult> CompileServerResults() {
            return ResponseEntities.Select(x => x.Value).ToList();
        }

        virtual protected void GenerateIdentifier() {
            var ThisId = this.GetType().ToString();
            foreach (var Item in this.RequestEntities.Keys) {
                ThisId += "&[" + Item + ":" + RequestEntities[Item].EntityGuid.ToString() + "]";
            }
            foreach (var Item in this.MessageData.Keys) {
                ThisId += "&(" + Item + ":" + MessageData[Item].ToString().ToString() + "]";
            }
            MessageIdentifier = ThisId;
        }

        protected void MarkBusy() {
            _IsBusyRefCount++;
            RaisePropertyChanged("IsBusy");
        }
        protected void MarkIdle() {
            if (_IsBusyRefCount == 0)
                throw new InvalidOperationException("Can not MarkIdle on an already Idle message.");

            _IsBusyRefCount--;
            RaisePropertyChanged("IsBusy");
        }
        protected void MarkAsyncStarted() {
            MarkAsyncStarted(1);
        }
        protected void MarkAsyncStarted(int count) {
            
            _IsInFlightRefCount += count;
            RaisePropertyChanged("IsBusy");
            RaisePropertyChanged("IsInFlight");
        }
        protected void MarkAsyncFinished() {
            //if (_IsInFlightRefCount == 0)
            //    throw new InvalidOperationException("Can not MarkAsyncFinished on a message with no pending async operations.");
            
            _IsInFlightRefCount--;
            if (_IsInFlightRefCount < 0) {
                _IsInFlightRefCount = 0;
            }
            RaisePropertyChanged("IsBusy");
            RaisePropertyChanged("IsInFlight");
        }
        protected IInsuranceAdjusterDataService GetDataService() {
            return Message.GetDataServiceFromSelector();
        }
        protected void SetMessageData<T>(String key, T value) {
            if (MessageData.ContainsKey(key))
                MessageData[key] = value;
            else
                MessageData.Add(key, value);
        }
        protected T GetMessageData<T>(String key, T defaultValue = default(T)) {
            if (MessageData.ContainsKey(key))
                return (T)MessageData[key];
            else
                return defaultValue;
        }

        #region INotifyPropertyChanged
        public event PropertyChangedEventHandler PropertyChanged;
        protected void RaisePropertyChanged(string propertyName) {
            if (PropertyChanged != null) {
                Deployment.Current.Dispatcher.BeginInvoke(() => { PropertyChanged.Invoke(this, new PropertyChangedEventArgs(propertyName)); });
            }
        }
        #endregion
    }

    [Flags]
    public enum MessageDataSource {
        /// <summary>
        ///  Reference Data that doesn't change that often
        /// </summary>
        CacheOnly = 1,
        /// <summary>
        /// Online Only Data
        /// </summary>
        ServerOnly = 2,
        /// <summary>
        /// Quick Load, with updates from the server (If Changed)
        /// </summary>
        CacheAndServer = 3,
        /// <summary>
        /// Get from server when online, from cache when offline
        /// </summary>
        PreferServerOverCache = 7,
        /// <summary>
        /// 
        /// </summary>
        FromServerOnlyIfNoCacheOption = 128,
    }
    public enum MessageStatus {
        Created = 0,
        InQueue = 1,
        ProcessingCache = 2,
        WaitingForNetworkConnection = 3,
        ProcessingServer = 4,
        Completed = 5,
        Redundent = 6,
        Aborted = 7,
        Preparing = 8,
    }
    public enum Verb {
        Create = 0,
        Read = 1,
        Update = 2,
        Delete = 3,
    }
}
